# -*- coding: utf-8 -*-
"""
Created on Wed Feb  7 15:36:23 2018
生成器

通过列表生成式，我们可以直接创建一个列表。但是，受到内存限制，列表容量肯定是有限的。
而且，创建一个包含100万个元素的列表，不仅占用很大的存储空间，如果我们仅仅需要访问前面
几个元素，那后面绝大多数元素占用的空间都白白浪费了。

所以，如果列表元素可以按照某种算法推算出来，那我们是否可以在循环的过程中不断推算出后续
的元素呢？这样就不必创建完整的list，从而节省大量的空间。
在Python中，这种一边循环一边计算的机制，称为生成器：generator。

@url https://www.liaoxuefeng.com/
@author: michaelxu
"""

# 要创建一个generator，有很多种方法。
# 第一种方法很简单，只要把一个列表生成式的[]改成()，就创建了一个generator
# 列表生成器
L=[x*x for x in range(10)]
# 生成器generator
g=(x*x for x in range(10))

# 创建L和g的区别仅在于最外层的[]和()，L是一个list，而g是一个generator
# >>> L
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# >>> g
# <generator object <genexpr> at 0x1022ef630>

# 从生成器generator中取元素,2种方式：
# 1. 调用next(g)
# 2. 调用for循环
# generator保存的是算法，每次调用next(g)，就计算出g的下一个元素的值，直到计算到最后一个元素，
# 没有更多的元素时，抛出StopIteration的错误
# 我们创建了一个generator后，基本上永远不会调用next()，
# 而是通过for循环来迭代它，并且不需要关心StopIteration的错误
for i in g:
    print(i)
 
    
# 斐波拉契数列
# 注意，赋值语句：
# a, b = b, a+b
# 相当于
# t = (b, a + b) # t是一个tuple
# a = t[0]
# b = t[1]
def fib(max):
    n, a, b = 0,0,1
    while n<max:
        print(b)
        a, b = b, a+b
        n += 1
    return "done"


# 要把fib函数变成generator，只需要把print(b)改为yield b就可以了
# 这就是定义generator的另一种方法。
# 如果一个函数定义中包含yield关键字，那么这个函数就不再是一个普通函数，而是一个generator：
def fibg(max):
    n, a, b = 0,0,1
    while n<max:
        yield b
        a, b = b, a+b
        n += 1
    return "done"


# 最难理解的就是generator和函数的执行流程不一样。
# 函数是顺序执行，遇到return语句或者最后一行函数语句就返回。
# 而变成generator的函数，在每次调用next()的时候执行，
# 遇到yield语句返回，再次执行时从上次返回的yield语句处继续执行
def odd():
    print("step 1:")
    yield 1
    print("step 2:")
    yield 2
    print("step 3:")
    yield 3
    
o = odd()
next(o)
next(o)
next(o)
#next(o)

# 但是用for循环调用generator时，发现拿不到generator的return语句的返回值。
# 如果想要拿到返回值，必须捕获StopIteration错误，返回值包含在StopIteration的value中
g=fibg(6)
while True:
    try:
        x=next(g)
        print('g:',x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break

# 杨辉三角
def triangles(n):
    x=[1]
    i=0
    while i<n:
        yield x
        y=[x[i]+x[i+1] for i in range(len(x)-1)]
        y.insert(0, 1)
        y.append(1)
        x=y
        i += 1
        

n = 0
results = []
for t in triangles(10):
    print(t)
    results.append(t)
    n = n + 1
    if n == 10:
        break
if results == [
    [1],
    [1, 1],
    [1, 2, 1],
    [1, 3, 3, 1],
    [1, 4, 6, 4, 1],
    [1, 5, 10, 10, 5, 1],
    [1, 6, 15, 20, 15, 6, 1],
    [1, 7, 21, 35, 35, 21, 7, 1],
    [1, 8, 28, 56, 70, 56, 28, 8, 1],
    [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
]:
    print('测试通过!')
else:
    print('测试失败!')
    

def triangles2(n):
    x=[1]
    i=0
    while i<n:
        yield x
        x=[1]+[x[i]+x[i+1] for i in range(len(x)-1)]+[1]
        i += 1


t=triangles2(8)
for i in t:
    print(i)

